#include <X11/Xlib.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include "iostream"
#include "string.h"
#include "fstream"
#include <time.h>
//#include "dos.h"
//#include "stdafx.h"

using namespace std;

#define HERE printf("%s, %d\n",__FILE__,__LINE__);
#define MAX_ENTRIES 50
#define NUM_RESOLVER_THREADS 5
#define NUM_WORKER_THREADS 5
#define MAX_THREADS NUM_WORKER_THREADS*3+1
#define AUTHENTICATOR 2
#define GUI           3
#define CACHE_SIZE    4
pthread_t threads[MAX_THREADS];
pthread_t worker_threads[NUM_WORKER_THREADS];
pthread_mutex_t lock;

int i, j;
int file_id;
void dispatch(); 

class cache {
    int head, tail, size;
    int files[CACHE_SIZE];

    public:
      cache() {
        size =  0;
        tail =  0;
        head = -1;
      }

      // Push the file onto the cache if it isn't already cached. If the size
      // is less than the max, increment size and update head only; otherwise
      // update both head and tail. Given the nature of the algorithm, pushing
      // pops the LRU if size==CACHE_SIZE.
      void *push(void *ldi_ptr) {
        if (cached(ldi_ptr)) return (void *) false;
        int ldi = (intptr_t) ldi_ptr;
        ++head %= CACHE_SIZE;
        if (size < CACHE_SIZE) size++;
        else ++tail %= CACHE_SIZE;
        files[head] = ldi;
        return (void *) true;
      }

      // Check to see if the file is already cached.
      void *cached(void *ldi_ptr) {
        int ldi = (intptr_t) ldi_ptr;
        for (int i=0; i<size; i++) 
          if (files[(tail+i)%CACHE_SIZE] == ldi)
            return (void *) true;
        return (void *) false;
      }

      // Retrieve the file and arrange the caching array in a stable fashion so
      // that the one retrieved lies at the head of the array.  This way, the
      // tail always holds the LRU.
      void *retrieve(void *ldi_ptr) {
        // Retrieving requires pushing in the case of files not already in cache.
        push(ldi_ptr); 
        int I, j, J, Head, tmp, ldi = (intptr_t) ldi_ptr;
        for (i=0; i<size; i++) {
          I = (tail+i)%CACHE_SIZE;
          if (files[I] == ldi) {
            tmp = files[I];
            Head = head>I ? head : head+CACHE_SIZE;
            for (j=I; j<Head; j++) {
              J = j%CACHE_SIZE;
              files[J] = files[(J+1)%CACHE_SIZE];
            }
            files[head] = tmp;
            print();
            return (void *) files[head];
          }
        }
        return (void *) -1;
      }

      void print() {
        int I;
        for (i=0; i<size; i++) {
          I = (tail+i) % CACHE_SIZE;
          cout << I << ":" << files[I] << "; ";
        }
        cout << "\n";
      }
};

// This structure contains all necessary data
// to be passed to the cacher function. 
struct cache_data {
  cache *c;
  int  ldi;
}; 

void *cacher(void *c_data_ptr) {
  struct cache_data *c_data; 
  c_data = (struct cache_data *) c_data_ptr;
  (c_data->c)->retrieve((void *)(c_data->ldi));
  return NULL;
}

// Structure for each request.
struct request_data {
  char ip[16];
  char timestamp[256];
  char filename[256]; 
  int  ldi;
  int  authz;
  int  type;
  int  done;
  int  rtid;
};


// The request queue keeps all the requests recieved so far.
class request_queue {
  struct request_data *r_data[50];
  int head, tail;
public:

  request_queue() {
    head = -1;
    tail = -1;
  }

  void push(request_data *r) {
                r_data[++head] = new struct request_data;
      strcpy(r_data[head]->ip, r->ip);
      strcpy(r_data[head]->timestamp, r->timestamp);
      strcpy(r_data[head]->filename, r->filename);
      r_data[head]->ldi = r->ldi;
      r_data[head]->authz = r->authz;
      r_data[head]->type = r->type;
  }

  void *pop() {
    if (tail == head) {
      return NULL;
    }
    tail++;
    return (void *) r_data[tail];
  }
}req_Q;

class queue {

    // The queue contains job information:
    // IP of the requester, timestamp, filename, and instruction.
    int head, tail, length;
    char ip[MAX_ENTRIES][16]; 
    char time[MAX_ENTRIES][256];
    char filename[MAX_ENTRIES][256];
    int  type[MAX_ENTRIES];

public:

    // Initialize queue.
    queue() { head = -1; }

    // Push entry onto queue for processing.
    void push(char *i, char *t, bool s, char *f) {
      head++;
      strcpy(ip[head],    i);
      strcpy(filename[head],f);
      strcpy(time[head],    t);
      type[head]          = s;
    }

    // Accessor functions.
    char *get_ip() {
      if (empty()) return NULL;
      else return ip[head];
    }

    char *get_time() {
      if (empty()) return NULL;
      else return time[head];
    }

    bool get_type() {
      if (empty()) return NULL;
      else return type[head];
    }

    char *get_filename() {
      if (empty()) return NULL;
      else return filename[head];
    }

    // Exit if queue is empty.
    bool empty() {
      if (head<0) return false;
      else        return true;
    }
};



// Find the local document identifier in the permissions table and store its
// permissions in the request_data structure.
void *authenticate(void *r_data_ptr) {
  char ip[16];
  bool found = false;
  int  num, ldi, authz; 
  FILE *authfile = fopen("auth.txt", "r");
  struct request_data *r_data = (struct request_data *) r_data_ptr;

  while (!feof(authfile)) {
    fscanf(authfile, "%d(%d)\n", &ldi, &num);
    if (ldi == r_data->ldi)
      for (i=0; i<num; i++) {
        fscanf(authfile, " %s %d\n", ip, &authz);
        if (0==strcmp(ip, r_data->ip)) {
          r_data->authz = authz;
          found = true; 
          break;
        }
      }
  }

  fclose(authfile);
  if (!found) r_data->authz = -1;
  return NULL;
}

// Check the directory structure file to see if the requested file name appears
// within it. If it does, send the local document identifier along with the IP
// to the authX thread to ensure that the IP has permission to access it.
void *resolve (void *r_data_ptr) {
  char filename[256]; 
  bool found = false;
  int  ldi, num, timestamp; 
  char dir;
  FILE *dirstruct = fopen("directory.txt", "r");
  struct request_data *r_data = (struct request_data *) r_data_ptr;

  while (fscanf(dirstruct, "%c (%d)\n", &dir,&num)) {
  for (i=0; i<num; i++) {
    fscanf(dirstruct, " %d %s %d\n", &ldi, filename, &timestamp);
      if (0==strcmp(filename, r_data->filename)) {
        found = true;
        break;
      } else {
        fclose(dirstruct);
        return NULL;
      }
    }
  }
   
  if (found) {
    r_data->ldi = ldi;
    pthread_create(&threads[AUTHENTICATOR*NUM_RESOLVER_THREADS+r_data->rtid], NULL, authenticate, (void *)r_data);
  }

  fclose(dirstruct);
  pthread_join(threads[AUTHENTICATOR*NUM_RESOLVER_THREADS+r_data->rtid], NULL); 
  r_data->done = 1;
  return NULL;
}

// Read the input file line by line. Push all the relevant information onto the
// queue. Then create a resolve thread for the request, passing a pointer to a
// structure containing the input data as a parameter.
void *dispatch(void *threadid) {
  struct request_data *r_data[NUM_RESOLVER_THREADS+1];
  char *instruction;
  void *exit_status;
  int  tid;

  for (i=0; i<=NUM_RESOLVER_THREADS; i++) {
    r_data[i] = new struct request_data;
    r_data[i]->done = 1;
  }

  queue q;
  FILE *input = fopen("input.txt", "r");
  while (!feof(input)) {
    while (true) {
      i = ((i+1) % NUM_RESOLVER_THREADS) + 1;
      if (1 == r_data[i]->done) {
        tid = i;
        r_data[tid]->done = 0;
        break;
      }
    }
    fscanf(input, "%s %s %s %s\n", r_data[tid]->ip, r_data[tid]->timestamp, instruction, r_data[tid]->filename); 
    if (0==strcmp(instruction, "GET")) r_data[tid]->type = 0;
    else                               r_data[tid]->type = 1;

    q.push(r_data[tid]->ip, r_data[tid]->timestamp, r_data[tid]->type, r_data[tid]->filename);
    req_Q.push(r_data[tid]);
    if (pthread_create(&threads[tid], NULL, resolve, (void *)r_data[tid])) 
       cout << "Error creating thread.\n";
  }

  fclose(input);
  return NULL;
}

void *gui(void *message_ptr) {
   Display *d;
   Window w;
   XEvent e;
   int s;
   char *msg = (char *) message_ptr;
 
   d = XOpenDisplay(NULL);
   if (d == NULL) {
      fprintf(stderr, "Cannot open display\n");
      exit(1);
   }
 
   s = DefaultScreen(d);
   w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 100, 100, 1,
                           BlackPixel(d, s), WhitePixel(d, s));
   XSelectInput(d, w, ExposureMask | KeyPressMask);
   XMapWindow(d, w);
 
   while (1) {
      XNextEvent(d, &e);
      if (e.type == Expose) {
         XFillRectangle(d, w, DefaultGC(d, s), 20, 20, 10, 10);
         XDrawString(d, w, DefaultGC(d, s), 10, 50, msg, strlen(msg));
      }
      if (e.type == KeyPress)
         break;
   }
   XCloseDisplay(d);
}

void *worker(void *id){
  struct request_data *r_data;
  bool found;
  char dir;
  char filename[256];
  char timestamp[256];
  int  num, ldi;
  srand (time(NULL));
  while (1) {
    for (int i=0; i<rand(); i++) {}
    pthread_mutex_lock(&lock);
    r_data = (request_data *) req_Q.pop();
    if (r_data) {
      if (r_data->authz != 1) {
        char *message = "Not authorized to perform this request";
        if (pthread_create(&threads[MAX_THREADS-(intptr_t)id], NULL, gui, (void *)message)) 
          cout << "Error creating thread.\n";
        pthread_join(threads[MAX_THREADS-(intptr_t)id], NULL);
      } else {
        FILE *dirstruct = fopen("directory.txt", "a+");
        found = false;
        while (fscanf(dirstruct, "%c (%d)\n", &dir,&num)) {
          for (j=0; j<num; j++) {
            fscanf(dirstruct, " %d %s %s\n", &ldi, filename, timestamp);
            if (0==strcmp(filename, r_data->filename)) {
              found = true;
              fclose(dirstruct);
              break;
            } 
          }
        }

        if (0 == r_data->type) {
          if (found == true) {
            if (pthread_create(&threads[MAX_THREADS-(intptr_t)id], NULL, gui, (void *)r_data->filename))
              cout << "Error creating thread.\n";
            pthread_join(threads[MAX_THREADS-(intptr_t)id], NULL);
          } else { 
            if (pthread_create(&threads[MAX_THREADS-(intptr_t)id], NULL, gui, (void *) "File does not exist."))
              cout << "Error creating thread.\n";
            pthread_join(threads[MAX_THREADS-(intptr_t)id], NULL);
          }
          fclose(dirstruct);
          return NULL;
        }
        else if (1 == r_data->type) {
          if (found == true) {
            if (pthread_create(&threads[MAX_THREADS-(intptr_t)id], NULL, gui, (void *)r_data->filename))
              cout << "Error creating thread.\n";
            pthread_join(threads[MAX_THREADS-(intptr_t)id], NULL);
            }
            fclose(dirstruct);
            return NULL;
        }

          if (found == false) {
            fprintf(dirstruct, "%d %s %d\n", file_id++, r_data->filename, r_data->timestamp);
            if (pthread_create(&threads[MAX_THREADS-(intptr_t)id], NULL, gui, (void *)r_data->filename))
              cout << "Error creating thread.\n";
            pthread_join(threads[MAX_THREADS-(intptr_t)id], NULL);
          }
            fclose(dirstruct);
            return NULL;
        }
    }
  pthread_mutex_unlock(&lock);
  }
}

// Simply pass control to the dispatcher thread and initialize some worker threads.
int main(int argc, char *argv[])
{

   int tid = 0;
   file_id = 11;
   if (pthread_create(&threads[0], NULL, dispatch, (void *)tid))
     cout << "Error creating thread.\n";
   pthread_mutex_init(&lock, NULL);
   for(int i = 0; i < NUM_WORKER_THREADS; i++) {
    if (pthread_create(&worker_threads[i], NULL, worker, (void *)tid))
    cout << "Error creating worker thread.\n";
   }
   pthread_join(threads[0], NULL);
   pthread_exit(NULL);

/* 
   struct cache_data *c_data;
   c_data->c = new cache;

   int i = 0;
   for (i=0; i<2*CACHE_SIZE; i++) {
     c_data->ldi = i;
     if (pthread_create(&threads[0], NULL, cacher, c_data))
       cout << "Error creating thread!";
     pthread_join(threads[0], NULL);
   }

   pthread_exit(NULL);
*/

}
